﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Collections.ObjectModel;

namespace BlueLotus7.SSQAnalysisCore
{
    public class Analyzer
    {
        public SSQLotteryStage[] ssqLotteryCollection
        {
            get;
            protected set;
        }

        public Analyzer(List<SSQLotteryStage> lotteryList)
        {
            if (lotteryList != null)
            {
                this.ssqLotteryCollection = lotteryList.ToArray();
            }
            else
            {
                this.ssqLotteryCollection = new SSQLotteryStage[] { };
            }
        }

        #region Public Methods

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public AnalysisResult Analyze()
        {
            return Analyze(null, null);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="startIdentity"></param>
        /// <param name="endIdentity"></param>
        /// <returns></returns>
        public AnalysisResult Analyze(string startIdentity, string endIdentity)
        {
            AnalysisResult result = new AnalysisResult();

            List<SSQLotteryStage> sources = new List<SSQLotteryStage>();
            if (this.ssqLotteryCollection != null && this.ssqLotteryCollection.Length > 0)
            {
                foreach (var one in this.ssqLotteryCollection)
                {
                    if ((string.IsNullOrWhiteSpace(startIdentity) || startIdentity.CompareTo(one.LotteryIdentity) <= 0)
                        && (string.IsNullOrWhiteSpace(endIdentity) || endIdentity.CompareTo(one.LotteryIdentity) >= 0))
                    {
                        sources.Add(one.Clone() as SSQLotteryStage);
                    }
                }

                result = Analyze(sources);
            }

            return result;
        }

        #endregion

        /// <summary>
        /// Gets fully analysis result according to lottery sources.
        /// </summary>
        /// <param name="sources">Lottery sources.</param>
        /// <returns>Analysis result.</returns>
        public static AnalysisResult Analyze(List<SSQLotteryStage> sources)
        {
            AnalysisResult result = new AnalysisResult();

            if (sources != null && sources.Count > 0)
            {
                sources.SortByIdentity();

                result.CatalogueIdentityStart = sources[0].LotteryIdentity;
                result.CatalogueIdentityEnd = sources[sources.Count - 1].LotteryIdentity;
                result.TotalStageCount = sources.Count;

                /// Fill ball martix: History + Offset
                FillBallMartix(sources, result);

                /// Fill basic info: Relevance + Hit Count
                FillBasicInfo(sources, result);

                /// CalculateBasePercentage
                CalculateBasePercentage(result);

                /// FillRelevenceCalculatedValue
                FillRelevenceCalculatedValue(result);

                /// CalculateRelevencePercentage
                CalculateRelevencePercentage(result);

            }

            return result;
        }

        /// <summary>
        /// Generate ball martix by given lottery sources.
        /// </summary>
        /// <param name="sources">Lottery sources.</param>
        /// <param name="result">Analysis result to fill.</param>
        protected static void FillBallMartix(List<SSQLotteryStage> sources, AnalysisResult result)
        {
            if (result != null && sources != null && sources.Count > 0)
            {
                result.SSQMartix.Clear();

                SSQMartixItem lastNode = new SSQMartixItem(string.Empty);
                foreach (var one in sources)
                {
                    lastNode.LotteryIdentity = one.LotteryIdentity;

                    for (int i = 1; i <= one.RedBalls.Length; i++)
                    {
                        if (one[i])
                        {
                            lastNode.RedBalls[i] = 0;
                        }
                        else
                        {
                            lastNode.RedBalls.IncreaseValue(i, 1);
                        }
                    }
                    for (int i = 1; i <= Constant.BlueBallQuantity; i++)
                    {
                        if (one.BlueBall == i)
                        {
                            lastNode.BlueBalls[i] = 0;
                        }
                        else
                        {
                            lastNode.BlueBalls.IncreaseValue(i, 1);
                        }
                    }

                    lastNode.Key = Guid.NewGuid();
                    result.SSQMartix.Add(lastNode.Clone() as SSQMartixItem);
                }

                result.SetOffetCount(lastNode);
            }
        }

        /// <summary>
        /// Generate ball basic information by given lottery sources.
        /// </summary>
        /// <param name="sources"></param>
        /// <param name="result"></param>
        protected static void FillBasicInfo(List<SSQLotteryStage> sources, AnalysisResult result)
        {
            if (sources != null && result != null)
            {
                result.HitCount.Clear();
                for (int i = 1; i <= Constant.RedBallQuantity; i++)
                {
                    FillBasicInfo(sources, i, result, false);
                }
                for (int i = 1; i <= Constant.BlueBallQuantity; i++)
                {
                    FillBasicInfo(sources, i, result, true);
                }
            }
        }

        /// <summary>
        /// Generate ball basic information by given lottery sources.
        /// </summary>
        /// <param name="sources"></param>
        /// <param name="number"></param>
        /// <param name="result"></param>
        /// <param name="isBlueBall"></param>
        protected static void FillBasicInfo(List<SSQLotteryStage> sources, int number, AnalysisResult result, bool isBlueBall)
        {
            if (sources != null && sources.Count > 0 && result != null)
            {
                int rowNumber = 0;
                int hitCount = 0;

                result.Relevance[number].Clear();

                foreach (SSQLotteryStage one in sources)
                {
                    bool isHit = isBlueBall ? (one.BlueBall == number) : one[number];

                    if (isHit)
                    {
                        //Add total hit count
                        hitCount += 1;

                        //Analyze relevance for other numbers.
                        var relevanceItem = result.Relevance[number];
                        if (isBlueBall)
                        {
                            result.HitCount.BlueBalls[number] = hitCount;
                        }
                        else
                        {
                            for (int j = 1; j <= Constant.RedBallQuantity; j++)
                            {
                                relevanceItem.IncreaseValue(j, (one[j] ? 1 : 0));
                            }
                            result.HitCount.RedBalls[number] = hitCount;
                        }
                    }

                    rowNumber++;
                }
            }
        }

        /// <summary>
        /// Calculates base percentage
        /// </summary>
        /// <param name="result"></param>
        protected static void CalculateBasePercentage(AnalysisResult result)
        {
            if (result != null)
            {
                for (int i = 1; i <= Constant.RedBallQuantity; i++)
                {
                    CalculateBasePercentage(result, i, false);
                }

                for (int i = 1; i <= Constant.BlueBallQuantity; i++)
                {
                    CalculateBasePercentage(result, i, true);
                }
            }
        }

        /// <summary>
        /// Calculates base percentage
        /// </summary>
        /// <param name="result"></param>
        /// <param name="number"></param>
        /// <param name="isBlueBall"></param>
        protected static void CalculateBasePercentage(AnalysisResult result, int number, bool isBlueBall)
        {
            if (result != null)
            {
                int missingCount = (int)(isBlueBall ? result.OffsetCount.BlueBalls[number] : result.OffsetCount.RedBalls[number]);
                //As next missing.
                missingCount = missingCount + 1;
                decimal hit = isBlueBall ? result.HitCount.BlueBalls[number] : result.HitCount.RedBalls[number];
                decimal averageP = Divide(hit, (decimal)(result.TotalStageCount));
                if (isBlueBall)
                {
                    result.BasePercentage.BlueBalls[number] = CalculateNextBinomial(averageP, missingCount);
                }
                else
                {
                    result.BasePercentage.RedBalls[number] = CalculateNextBinomial(averageP, missingCount);
                }
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="p"></param>
        /// <param name="n"></param>
        /// <returns></returns>
        protected static decimal CalculateBinomial(decimal p, int n)
        {
            return (decimal)n * p * ((decimal)(Math.Pow((double)(1 - p), n - 1)));
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="p"></param>
        /// <param name="n"></param>
        /// <returns></returns>
        protected static decimal CalculateNextBinomial(decimal p, int n)
        {
            return (decimal)(Math.Pow((double)(CalculateBinomial(p, n)), 1.0d / (double)n));
            //  return (decimal)(Math.Log((double)(CalculateBinomial(p, n)), n));
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="min"></param>
        /// <param name="max"></param>
        /// <param name="average"></param>
        /// <returns></returns>
        protected static decimal CalculateMostPossibleByAverageFormula(decimal min, decimal max, decimal average)
        {
            return (6 * average - min - max) / 4;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        public static decimal Divide(decimal a, decimal b)
        {
            return (a == 0 || b == 0) ? 0 : (a / b);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="result"></param>
        protected static void FillRelevenceCalculatedValue(AnalysisResult result)
        {
            if (result != null)
            {
                result.PRMartix.Clear();

                /// Fill PR martix.
                foreach (var one in result.SSQMartix)
                {
                    List<int> hitNumbers = new List<int>();
                    for (int i = 1; i <= Constant.RedBallQuantity; i++)
                    {
                        if (one.RedBalls[i] == 0)
                        {
                            hitNumbers.Add(i);
                        }
                    }
                    foreach (var ball in hitNumbers)
                    {
                        foreach (var ball2 in hitNumbers)
                        {
                            result.PRMartix.GetRow(ball).IncreaseValue(ball2, 1);
                        }
                    }
                }
            }
        }

        protected static void CalculateRelevencePercentage(AnalysisResult result)
        {
            if (result != null)
            {
                result.CalculatedPercentage[0].Clear();

                /// Analyze PR martix.
                for (int i = 1; i <= Constant.RedBallQuantity; i++)
                {
                    var current = result.PRMartix.GetRow(i);
                    for (int j = 1; j <= Constant.RedBallQuantity; j++)
                    {
                        var rValue = Analyzer.Divide(current[j], result.HitCount.RedBalls[j]);
                        current[j] = CalculateRelevenceValue(rValue, result.BasePercentage.RedBalls[j]);
                    }

                    var sortedItems = current.GetBallSortedList();
                    for (int k = 0; k < 6; k++)
                    {
                        var item = sortedItems[k];
                        result.CalculatedPercentage[0][item.Key] += item.Value;
                    }
                }

                for (int i = 1; i <= 6; i++)
                {
                    result.CalculatedPercentage[i].Clear();

                    for (int j = 1; j <= Constant.RedBallQuantity; j++)
                    {
                        var current = result.PRMartix.GetRow(j);
                        for (int k = 1; k <= Constant.RedBallQuantity; k++)
                        {
                            var rValue = Analyzer.Divide(current[j], result.HitCount.RedBalls[j]);
                            current[j] = CalculateRelevenceValue(rValue, result.BasePercentage.RedBalls[j], (decimal)(Math.Pow(6 / 33, i)));
                        }

                        var sortedItems = current.GetBallSortedList();
                        for (int k = 0; k < 6; k++)
                        {
                            var item = sortedItems[k];
                            result.CalculatedPercentage[i][item.Key] += item.Value;
                        }
                    }
                }

                BallCollection resultCollection = new BallCollection(Constant.RedBallQuantity);

                for (int i = 1; i <= Constant.RedBallQuantity; i++)
                {
                    for (int j = 0; j <= 6; j++)
                    {
                        resultCollection.IncreaseValue(i, result.CalculatedPercentage[j][i]);
                    }
                }
                var top6Numbers = resultCollection.GetBallSortedList();
                for (int k = 0; k < 6; k++)
                {
                    result.Result.RedBalls[top6Numbers[k].Key] = top6Numbers[k].Value;
                }
                var topBlueNumber = result.BasePercentage.BlueBalls.GetBallSortedList();
                result.Result.RedBalls[topBlueNumber[0].Key] = topBlueNumber[0].Value;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="relevence"></param>
        /// <param name="basePercentage"></param>
        /// <returns></returns>
        protected static decimal CalculateRelevenceValue(decimal relevence, decimal basePercentage, decimal k = 1)
        {
            return basePercentage * relevence * k;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="pAB"></param>
        /// <param name="pB"></param>
        /// <returns></returns>
        protected static decimal CalculateConditionalProbability(decimal pAB, decimal pB)
        {
            return Divide(pAB, pB);
        }
    }
}
